Basic try-catch-finally Exception Handling in Java

概要内容
1.访问栈解析
2.抛出异常
3.捕获异常
4.上诉异常
5.案例:捕获IOException
6.案例:上报IOException
7.finally关键字
8.捕获或上报异常的策略

这片文章基于如何try..catch…finally 处理,这个案例是java语言,但是同样适用于c#.java和c#在异常处理上不同的地方就是c#没有检查性异常.这在后续文章讨论.
异常在程序中的表示一些错误或不可预料的情况发生了.应该要执行一些操作能让程序刘继续运行,直到异常被处理.一个方法可能会抛出很多异常,例如输入参数和无效的参数.

访问栈解释
call stack多次提到这个概念是因为 访问栈意味着一系列访问当前方法和返回到主方法的程序流.如果方法A访问B,B访问C,访问栈就是ABC.
当方法C返回到访问栈就只剩下A和B,如果B访问了方法D,访问栈就是ABD

了解访问栈是学习异常上报概念很重要的知识,异常被访问栈上报,从发生的地方抛出,直到访问栈catch住.

抛出异常
如果一个方法需要抛出异常,应该先在方法签名中申明这个异常,并且包含在throw语句的方法中.下面是案例:

1
2
3
4
5
6
7
public void divide(int numberToDivide, int numberToDivideBy)
throws BadNumberException{
if(numberToDivideBy == 0){
throw new BadNumberException("Cannot divide by 0");
}
return numberToDivide / numberToDivideBy;
}

当一个异常通过throw语句抛出后阻止了执行流继续.任何throw语句下面的的语句都不会执行.上面的案例numberToDivide / numberToDivideBy 不会执行.程序当异常被catch块捕获的时候,程序将恢复执行.捕获异常稍后解释.

你能抛出任何类型的异常,只需要你的方法签名声明了.你也能定义自己的异常,异常都是继承自java.lang.Exception.或者其他的内置异常类.如果一个方法声明抛出异常A,他的子类重写的A方法也应该声明.

捕获异常
检查性异常声明的方法,在被方法栈调用时强制捕获或者上报.捕获异常使用try..catch块:

1
2
3
4
5
6
7
8
9
10
public void callDivide(){
try {
int result = divide(2,1);
System.out.println(result);
} catch (BadNumberException e) {
//do something clever with the exception
System.out.println(e.getMessage());
}
System.out.println("Division attempt done");
}

BadNumberException 参数e指向异常抛出的地方.如果没有异常抛出在try块中,catch块会被忽略执行.
如果异常被try抛出,方法栈程序流callDivide被中止.在有catch块的访问栈能catch住抛出的异常,程序流也将恢复执行[译者:只有在catch块的方法能恢复,调用栈底层的方法上报的异常,底层是不会恢复执行的],上面的案例中System.out.println(result);不会执行,执行将执行catch块.如果catch当中抛出了异常没有被捕获,这个catch就会被终止,就像上面的try块.

当一个catch块完成程序将继续catch下面的后语, System.out.println(“Division attempt done”)将执行.

上报异常
你不必捕获从其他方法抛出的异常,如果你不能做任何事情的时候,你可以让方法上报这个异常到方法访问栈顶层,这样做就需要声明抛出异常,如下:

1
2
3
4
public void callDivide() throws BadNumberException{
int result = divide(2,1);
System.out.println(result);
}

try…catch块不见了,方法声明了可能抛出的异常类型,这个程序直到抛出异常被打断.”System.out.println(result);将不会执行,但是这个方法没有恢复,这个异常上报了.直到遇到catch块的访问栈捕获这个异常之前程序都不会恢复.抛出异常和捕获异常的方法在访问栈中都有他们自己执行停止,当异常抛出或上报时.

案例:捕获IOException
当一个异常抛出在一系列的语句中的try…catch中时,这一系列的语将直接被忽略直接跳到catch块.这样的代码被异常打断在很多地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void openFile(){
try {
// constructor may throw FileNotFoundException
FileReader reader = new FileReader("someFile");
int i=0;
while(i != -1){
//reader.read() may throw IOException
i = reader.read();
System.out.println((char) i );
}
reader.close();
System.out.println("--- File End ---");
} catch (FileNotFoundException e) {
//do something clever with the exception
} catch (IOException e) {
//do something clever with the exception
}
}

上面的语句如果new FileReader(“someFile”)抛出异常,下面try块没有语句会执行

上报IOException:

1
2
3
4
5
6
7
8
9
10
public void openFile() throws IOException {
FileReader reader = new FileReader("someFile");
int i=0;
while(i != -1){
i = reader.read();
System.out.println((char) i );
}
reader.close();
System.out.println("--- File End ---");
}

如果reader.read()方法抛出异常,程序将终止,异常通过访问栈传递到访问openFile()的栈顶.如果栈顶有try..catch快,异常被捕获.如果上层也只是抛出,这个访问openfile()的地方也会终止,异常再次上传.直到被catch住或者虚拟机来处理.

finally关键字
你能附加一个finally块到try…catch.finally块的代码始终会执行.如果你的try..catch中有return语句,finally块先执行,下面是案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void openFile(){
FileReader reader = null;
try {
reader = new FileReader("someFile");
int i=0;
while(i != -1){
i = reader.read();
System.out.println((char) i );
}
} catch (IOException e) {
//do something clever with the exception
} finally {
if(reader != null){
try {
reader.close();
} catch (IOException e) {
//do something clever with the exception
}
}
System.out.println("--- File End ---");
}
}

不管异常是否抛出,finally都会执行,上面的案例reader都会关闭.

注意:如果finally块抛出异常,没有捕获,finally块也将被打断就像try..catch一样,这就是上面为什么reader.close()方法要包含在try-catch中
你不必再catch和finally块,你只需要在其一使用try块,这个代码不会捕获,但是会让它上报给访问栈,因为finally块关闭这个文件阅读器,当异常抛出时.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void openFile() throws IOException {
FileReader reader = null;
try {
reader = new FileReader("someFile");
int i=0;
while(i != -1){
i = reader.read();
System.out.println((char) i );
}
} finally {
if(reader != null){
try {
reader.close();
} catch (IOException e) {
//do something clever with the exception
}
}
System.out.println("--- File End ---");
}
}

是捕获还是上报异常
你可能好奇你是否该捕获还是上报这个异常.这取决于具体的情形.在很多应用中你不能做太多除了高数请求程序当前失败.在这些应用程序通常可以捕获所有或大部分异常集中的第一个方法的调用堆栈,你能处理资源当异常上报后,例如,一个错误访问数据库连接的web程序,你鞥关闭连接在finally中,即使你不能做任何事情,只有告诉方法访问者访问失败,你最终处理异常还取决于你是否为应用程序选择检查或未经检查的异常。下面的张杰将会说明.